function(my_add_module MODULE_NAMES)
    set(MODULE_VERILATOR_ARGS -Wno-fatal --trace-fst)
    link_libraries(ddr_static)
    string(REGEX REPLACE ".*/(.*)" "\\1" CMAKE_BINARY_DIR_NAME ${CMAKE_BINARY_DIR})
    set(MODULE_RTL_DIR ${CMAKE_BINARY_DIR}/chisel-rtl)
    if (NOT EXISTS ${MODULE_RTL_DIR})
        if (USE_MILL)
            if (WIN32)
                set(MILLW_COMMAND ${X_ROOT}/millw.bat -i)
            else ()
                set(MILLW_COMMAND ${X_ROOT}/millw)
            endif ()
            set(MILLW_ARGS ChiselX.run)
            message(STATUS "${MILLW_COMMAND} ${MILLW_ARGS}")
            execute_process(COMMAND ${MILLW_COMMAND} ${MILLW_ARGS}
                    WORKING_DIRECTORY ${X_ROOT}
                    RESULT_VARIABLE MILL_OUT)
            if (NOT MILL_OUT EQUAL 0)
                message(FATAL_ERROR "Mill Bad exit status")
            endif ()
        else ()
            if (WIN32)
                set(SBT_COMMANDS "sbt -v \"run -tdp ${CMAKE_BINARY_DIR_NAME}\"")
                file(WRITE "${CMAKE_BINARY_DIR}/run-sbt.bat" "@${SBT_COMMANDS}")
                message(STATUS "${SBT_COMMANDS}")
                execute_process(COMMAND "${CMAKE_BINARY_DIR}/run-sbt.bat"
                        WORKING_DIRECTORY ${X_ROOT}
                        RESULT_VARIABLE SBT_OUT)
            else ()
                set(SBT_COMMANDS -v "run -tdp ${CMAKE_BINARY_DIR}")
                message(STATUS "sbt ${SBT_COMMANDS}")
                execute_process(COMMAND "${CMAKE_SOURCE_DIR}/sbt" "-sbt-version" "1.6.0" ${SBT_COMMANDS}
                        WORKING_DIRECTORY ${X_ROOT}
                        RESULT_VARIABLE SBT_OUT)
            endif ()

            if (NOT SBT_OUT EQUAL 0)
                message(FATAL_ERROR "SBT Bad exit status")
            endif ()
        endif ()
        set(MODULE_GENERATE_SKIP_ALL ON)
    else ()
        set(MODULE_GENERATE_SKIP_ALL OFF)
    endif ()
    set(MODULE_DEPS_LIST)
    # message("MODULE_NAMES = ${MODULE_NAMES}")
    foreach (MODULE_NAME ${MODULE_NAMES})
        message(STATUS "ChiselX Module: ${MODULE_NAME}")
        set(MODULE_UPPER)
        string(TOUPPER "${MODULE_NAME}" MODULE_UPPER)
        set(MODULE_GENERATE_SKIP ON)
        file(GLOB_RECURSE MODULE_SCALA_FILES "${MODULE_ROOT}/${MODULE_NAME}/*.scala")
        set(SCALA_CACHE_NAMES)

        set(MODULE_OUT_DIR ${PROJECT_BINARY_DIR}/verilator/${MODULE_NAME})
        file(MAKE_DIRECTORY ${MODULE_OUT_DIR})
        set(MODULE_FILE "${MODULE_ROOT}/${MODULE_NAME}/model/${MODULE_NAME}.cpp")
        file(GLOB_RECURSE MODULE_FILES "${MODULE_ROOT}/${MODULE_NAME}/*.cpp" "${MODULE_ROOT}/${MODULE_NAME}/*.hpp")
        include_directories("${MODULE_ROOT}/${MODULE_NAME}/model" "${MODULE_ROOT}/${MODULE_NAME}/model/include")
        message(STATUS "set module includes: ${MODULE_ROOT}/${MODULE_NAME}/model ${MODULE_ROOT}/${MODULE_NAME}/model/include")
        list(REMOVE_ITEM MODULE_FILES ${MODULE_FILE})

        if (${CONFIG_XM_${MODULE_UPPER}_RTL})
            set(MODULE_RTL_FILE "${MODULE_RTL_DIR}/${MODULE_NAME}.v")
            if (NOT EXISTS ${MODULE_RTL_FILE})
                message("Target RTL file not exists: ${MODULE_RTL_FILE}")
                set(MODULE_GENERATE_SKIP OFF)
            endif ()
            if (MODULE_GENERATE_SKIP OR MODULE_GENERATE_SKIP_ALL)
                foreach (SCALA_FILE ${MODULE_SCALA_FILES})
                    if (NOT MODULE_GENERATE_SKIP AND NOT MODULE_GENERATE_SKIP_ALL)
                        break()
                    endif ()
                    set(SCALA_HASH)
                    string(REGEX REPLACE ".+/(.+)\\.scala" "\\1" SCALA_NAME ${SCALA_FILE})
                    set(SCALA_CACHE_NAME SCALA_CACHE_${MODULE_NAME}_${SCALA_NAME})
                    # message("Scala File: ${SCALA_NAME} ${SCALA_HASH} ${SCALA_CACHE_NAME}=${${SCALA_CACHE_NAME}}")
                    file(READ ${SCALA_FILE} SCALA_CONTENT)
                    # message("CONTENT ${SCALA_CONTENT}")
                    if ("${SCALA_CONTENT}" STREQUAL "")
                        message("Ignore empty file: ${SCALA_NAME}")
                        continue()
                    endif ()
                    string(MD5 SCALA_HASH "${SCALA_CONTENT}")
                    # message("Scala File: ${SCALA_NAME} ${SCALA_HASH} ${SCALA_CACHE_NAME}=${${SCALA_CACHE_NAME}}")
                    list(APPEND SCALA_CACHE_NAMES ${SCALA_CACHE_NAME})
                    if (NOT DEFINED ${SCALA_CACHE_NAME})
                        message("Create cache ${SCALA_CACHE_NAME}=${SCALA_HASH}")
                        if (NOT MODULE_GENERATE_SKIP_ALL)
                            set(MODULE_GENERATE_SKIP OFF)
                        endif ()
                        set(${SCALA_CACHE_NAME} ${SCALA_HASH} CACHE INTERNAL "Scala File Hash Cache")
                    else ()
                        # message("is ${${SCALA_CACHE_NAME}} == ${SCALA_HASH}")
                        if (NOT "${${SCALA_CACHE_NAME}}" STREQUAL "${SCALA_HASH}")
                            message("Scala file changed: ${SCALA_NAME}")
                            if (NOT MODULE_GENERATE_SKIP_ALL)
                                set(MODULE_GENERATE_SKIP OFF)
                            endif ()
                            set(${SCALA_CACHE_NAME} ${SCALA_HASH} CACHE INTERNAL "Scala File Hash Cache")
                        endif ()
                    endif ()
                    if (NOT ${MODULE_GENERATE_SKIP})
                        break()
                    endif ()
                endforeach ()
            endif ()
            if (NOT MODULE_GENERATE_SKIP AND NOT MODULE_GENERATE_SKIP_ALL AND NOT IGNORE_SCALA_UPDATE)
                message(STATUS "Generating ${MODULE_NAME}")
                if (USE_MILL)
                    if (WIN32)
                        set(MILLW_COMMAND ${X_ROOT}/millw.bat -i)
                    else ()
                        set(MILLW_COMMAND ${X_ROOT}/millw)
                    endif ()
                    set(MILLW_ARGS ChiselX.run ${MODULE_NAME} -td ${CMAKE_BINARY_DIR_NAME}/chisel-tmp/${MODULE_NAME})
                    message(STATUS "${MILLW_COMMAND} ${MILLW_ARGS}")
                    execute_process(COMMAND ${MILLW_COMMAND} ${MILLW_ARGS}
                            WORKING_DIRECTORY ${X_ROOT}
                            RESULT_VARIABLE MILL_OUT)
                    if (NOT MILL_OUT EQUAL 0)
                        foreach (SCALA_CACHE_NAME ${SCALA_CACHE_NAMES})
                            set(${SCALA_CACHE_NAME} "" CACHE INTERNAL "Scala File Hash Cache")
                        endforeach ()
                        message(FATAL_ERROR "Mill Bad exit status")
                    else ()
                        set(BUILD_OK ON)
                    endif ()
                else ()
                    if (WIN32)
                        set(SBT_COMMANDS "sbt -v \"run ${MODULE_NAME} -td ${CMAKE_BINARY_DIR_NAME}/chisel-tmp/${MODULE_NAME}\"")
                        file(WRITE "${CMAKE_BINARY_DIR}/run-sbt.bat" "@${SBT_COMMANDS}")
                        message(STATUS "${SBT_COMMANDS}")
                        execute_process(COMMAND "${CMAKE_BINARY_DIR}/run-sbt.bat"
                                WORKING_DIRECTORY ${X_ROOT}
                                RESULT_VARIABLE SBT_OUT)
                    else ()
                        set(SBT_COMMANDS -v "run ${MODULE_NAME} -td ${CMAKE_BINARY_DIR_NAME}/chisel-tmp/${MODULE_NAME}")
                        message(STATUS "sbt ${SBT_COMMANDS}")
                        execute_process(COMMAND "${CMAKE_SOURCE_DIR}/sbt" "-sbt-version" "1.6.0" ${SBT_COMMANDS}
                                WORKING_DIRECTORY ${X_ROOT}
                                RESULT_VARIABLE SBT_OUT)
                    endif ()

                    if (NOT SBT_OUT EQUAL 0)
                        foreach (SCALA_CACHE_NAME ${SCALA_CACHE_NAMES})
                            set(${SCALA_CACHE_NAME} "" CACHE INTERNAL "Scala File Hash Cache")
                        endforeach ()
                        message(FATAL_ERROR "SBT Bad exit status")
                    else ()
                        set(BUILD_OK ON)
                    endif ()
                endif ()

                if (BUILD_OK)
                    message("Updating caches")
                    foreach (SCALA_FILE ${MODULE_SCALA_FILES})
                        string(REGEX REPLACE ".+/(.+)\\.scala" "\\1" SCALA_NAME ${SCALA_FILE})
                        set(SCALA_CACHE_NAME SCALA_CACHE_${MODULE_NAME}_${SCALA_NAME})
                        file(READ ${SCALA_FILE} SCALA_CONTENT)
                        string(MD5 SCALA_HASH "${SCALA_CONTENT}")
                        message("Scala Cache update: ${SCALA_NAME} ${SCALA_HASH} ${SCALA_CACHE_NAME}=${${SCALA_CACHE_NAME}}")
                        list(APPEND SCALA_CACHE_NAMES ${SCALA_CACHE_NAME})
                        set(${SCALA_CACHE_NAME} ${SCALA_HASH} CACHE INTERNAL "Scala File Hash Cache")
                    endforeach ()
                endif ()
            endif ()

            if (IGNORE_SCALA_UPDATE)
                message("Scala updates were Ignored...")
            endif ()

            add_executable(${MODULE_NAME} ${MODULE_FILE} ${MODULE_FILES} ${X_SOURCE_FILES})
            add_dependencies(${MODULE_NAME} ddr)
            target_compile_definitions(${MODULE_NAME} PRIVATE CONFIG_CHISEL_X=1)
            add_test(NAME ${MODULE_NAME} COMMAND ${MODULE_NAME})
            install(TARGETS ${MODULE_NAME} DESTINATION bin)
            message(STATUS "Chisel Module target: ${MODULE_NAME} -> ${MODULE_OUT_DIR} from ${MODULE_RTL_FILE}")
            verilate(${MODULE_NAME}
                    INCLUDE_DIRS "${MODULE_RTL_DIR}"
                    SOURCES ${MODULE_RTL_FILE}
                    PREFIX ${MODULE_NAME}
                    TOP_MODULE ${MODULE_NAME}
                    DIRECTORY ${MODULE_OUT_DIR}/${MODULE_NAME}
                    VERILATOR_ARGS ${MODULE_VERILATOR_ARGS})
            verilate(run-modules
                    INCLUDE_DIRS "${MODULE_RTL_DIR}"
                    SOURCES ${MODULE_RTL_FILE}
                    PREFIX ${MODULE_NAME}
                    TOP_MODULE ${MODULE_NAME}
                    DIRECTORY ${MODULE_OUT_DIR}/${MODULE_NAME}
                    VERILATOR_ARGS ${MODULE_VERILATOR_ARGS})
            if (${CONFIG_X_DIFFTEST} AND DEFINED CONFIG_XM_${MODULE_UPPER}_DIFF)
                set(MODULE_MODEL Model${MODULE_NAME})
                add_executable(${MODULE_MODEL} ${MODULE_FILE} ${MODULE_FILES} ${X_SOURCE_FILES})
                add_dependencies(${MODULE_MODEL} ddr)
                message(STATUS "Difftest Enable for ${MODULE_NAME}")
                message(STATUS "add_executable(${MODULE_MODEL} ${MODULE_FILE} ${MODULE_FILES} ${X_SOURCE_FILES})")
                target_compile_definitions(${MODULE_MODEL} PRIVATE CONFIG_CHISEL_X=1)
                target_compile_definitions(${MODULE_MODEL} PRIVATE USE_MODEL=1)
                target_compile_definitions(${MODULE_MODEL} PRIVATE DIFFTEST_${MODULE_NAME}=1)
                verilate(${MODULE_MODEL}
                        INCLUDE_DIRS "${MODULE_RTL_DIR}"
                        SOURCES ${MODULE_RTL_FILE}
                        PREFIX ${MODULE_NAME}
                        TOP_MODULE ${MODULE_NAME}
                        DIRECTORY ${MODULE_OUT_DIR}/${MODULE_MODEL}
                        VERILATOR_ARGS ${MODULE_VERILATOR_ARGS})
                add_test(NAME ${MODULE_MODEL} COMMAND ${MODULE_MODEL})
                install(TARGETS ${MODULE_MODEL} DESTINATION bin)
                add_dependencies(${MODULE_MODEL} nemu-ref)
            else ()
                message(STATUS "Difftest Disable for ${MODULE_NAME}")
            endif ()
            # message("CONFIG_XM_${MODULE_UPPER}_DEPS = ${CONFIG_XM_${MODULE_UPPER}_DEPS}")
            if (DEFINED CONFIG_XM_${MODULE_UPPER}_DEPS)
                message(STATUS "Add deps: ${MODULE_NAME} ${CONFIG_XM_${MODULE_UPPER}_DEPS}")
                string(REGEX REPLACE " " ";" MODULE_DEPS "${CONFIG_XM_${MODULE_UPPER}_DEPS}")
                foreach (DEP_NAME ${MODULE_DEPS})
                    if (${XM_${DEP_NAME}_RTL})
                        list(APPEND MODULE_DEPS_LIST "${MODULE_NAME}-${DEP_NAME}")
                    else ()
                        message("dep ${DEP_NAME} no RTL (${MODULE_NAME}-${DEP_NAME})")
                    endif ()
                endforeach ()
            endif ()
        else ()
            # No RTL Needed
            message(STATUS "Model Only for ${MODULE_NAME}")
            set(MODULE_MODEL Model${MODULE_NAME})
            add_executable(${MODULE_MODEL} ${MODULE_FILE} ${MODULE_FILES} ${X_SOURCE_FILES})
            add_dependencies(${MODULE_MODEL} ddr)
            message(STATUS "add_executable(${MODULE_MODEL} ${MODULE_FILE} ${MODULE_FILES} ${X_SOURCE_FILES})")
            target_compile_definitions(${MODULE_MODEL} PRIVATE CONFIG_CHISEL_X=1)
            target_compile_definitions(${MODULE_MODEL} PRIVATE USE_MODEL=1)
            target_compile_definitions(${MODULE_MODEL} PRIVATE MODEL_ONLY=1)
            add_test(NAME ${MODULE_MODEL} COMMAND ${MODULE_MODEL})
            install(TARGETS ${MODULE_MODEL} DESTINATION bin)
            add_dependencies(${MODULE_MODEL} nemu-ref)
        endif ()
    endforeach ()
    foreach (DEPS_ITEM ${MODULE_DEPS_LIST})
        # message("DEPS_ITEM=${DEPS_ITEM}")
        string(REGEX REPLACE "(.*)-(.*)" "\\1" MODULE_NAME "${DEPS_ITEM}")
        string(REGEX REPLACE "(.*)-(.*)" "\\2" DEP_NAME "${DEPS_ITEM}")
        message("Dep: ${MODULE_NAME}:${DEP_NAME}")
        verilate(Model${MODULE_NAME}
                INCLUDE_DIRS "${MODULE_RTL_DIR}"
                SOURCES "${MODULE_RTL_DIR}/${DEP_NAME}.v"
                PREFIX ${DEP_NAME}
                TOP_MODULE ${DEP_NAME}
                DIRECTORY ${MODULE_OUT_DIR}/${DEP_NAME}
                VERILATOR_ARGS ${MODULE_VERILATOR_ARGS})
    endforeach ()
endfunction()
